from __future__ import with_statement
from threading import Thread, Lock
from select import select
import socket
from bbworker import BBWorker
from bbpeer import BBPeer
from bitbrook import BitBrook
from time import time

class BBTracker(Thread):
    POLL = 1.0
    CLEANUP_INTERVAL = 60

    def __init__(self, address = None):
        super(BBTracker, self).__init__()

        self._address = address
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
        if address:
            self._sock.bind(address)
        else:
            self._sock.bind(('', 25784))
        self._sock.listen(5)

        self._brooks = {}
        self._brooks_cleanup_counter = 0
        self._brooks_lock = Lock()

        self._shutdown = False
        self._shutdown_signal = Lock()

    def has_brook(self, bid):
        with self._brooks_lock:
            return self._brooks.has_key(bid)

    def register_brook(self, bid, peer_addr):
        with self._brooks_lock:
            # Check that the bid does not already exist.
            if self._brooks.has_key(bid):
                # If you would rather have the peer simply add itself to the list of peers
                # and start seeding uncomment the next line.
                #self._brooks[bid].add_peer(BBPeer(peer_addr))
                raise Exception("The given brook ID already exists.")
            else:
                # Register the new brook.
                self._brooks[bid] = BitBrook(bid, BBPeer(peer_addr))

    def update_brook_activity(self, bid):
        with self._brooks_lock:
            # Check that the brook does indeed exist.
            if not self._brooks.has_key(bid):
                raise Exception("No such brook exists.")
            
            # Update the brook.
            self._brooks[bid].update_timestamp()

    def update_peer_activity(self, bid, peer_addr):
        with self._brooks_lock:
            # Check that the brook does indeed exist.
            if not self._brooks.has_key(bid):
                raise Exception("No such brook exists.")
            
            # Update the activity of the peer.
            self._brooks[bid].update_peer_activity(peer_addr)
            
    def remove_peer(self, bid, peer):
        with self._brooks_lock:
            # Check that the brook does indeed exist.
            if not self._brooks.has_key(bid):
                raise Exception("No such brook exists.")
            
            # Remove the peer from the brook.
            self._brooks[bid].remove_peer(peer)

    def request_peers(self, bid, peer_addr):
        with self._brooks_lock:
            # Check that the brook does indeed exist.
            if not self._brooks.has_key(bid):
                raise Exception("No such brook exists.")

            # Get a random selection of peers participating in the brook.
            peers = self._brooks[bid].get_random_peers()
            
            # Add the peer to the brook.
            self._brooks[bid].add_peer(BBPeer(peer_addr))

            # Return the peers.
            return peers
            
    def remove_stale_brooks(self):
        if self._brooks_cleanup_counter == 0:
            with self._brooks_lock:
                # Remove stale brooks, i.e., brooks with no seeders or leachers.
                now = time()
                for brook in self._brooks.values():
                    if now - brook.last_activity > BitBrook.TIMEOUT:
                        # Remove this brook.
                        self._brooks.pop(brook.bid)
                        continue

                    # Remove inactive peers from this active brook.
                    brook.remove_inactive_peers(now)
                    
        self._brooks_cleanup_counter = (self._brooks_cleanup_counter + 1) % BBTracker.CLEANUP_INTERVAL
                
    def shutdown(self, block = True):
        self._shutdown = True
        if block:
            self._shutdown_signal.acquire()
            self._shutdown_signal.release()

    def run(self):
        with self._shutdown_signal:
            while not self._shutdown:
                # Listen to connection attempts.
                (rd, _, _) = select([self._sock], [], [], BBTracker.POLL)
                if rd != []:
                    # We have a connection attempt.
                    try:
                        conn, addr = self._sock.accept()
                    except:
                        # The connection must have been lost. Just ignore that.
                        print "Connection lost."
                        continue

                    # We have a connection to a client. Spawn a worker thread to handle 
                    # the new connection.
                    worker = BBWorker(conn, addr, self)
                    worker.start()

                # In case of a timeout we use a little time doing some cleanup.
                self.remove_stale_brooks()
